import { type PianoSymbol, type PianoSymbolMapping } from '../types/types';

// 钢琴工具类
class PianoUtil {

    // 音名映射表
    private static mapping: PianoSymbolMapping = {
        'A0': 1,
        'B0': 3,
        '#A0': 2,
        '!B0': 2,
        '!C0': 3,

        'C1': 4,
        'D1': 6,
        'E1': 8,
        'F1': 9,
        'G1': 11,
        'A1': 13,
        'B1': 15,
        '#C1': 5,
        '#D1': 7,
        '#E1': 9,
        '#F1': 10,
        '#G1': 12,
        '#A1': 14,
        '#B1': 16,
        '!C1': 3,
        '!D1': 5,
        '!E1': 7,
        '!F1': 8,
        '!G1': 10,
        '!A1': 12,
        '!B1': 14,

        'C2': 16,
        'D2': 18,
        'E2': 20,
        'F2': 21,
        'G2': 23,
        'A2': 25,
        'B2': 27,
        '#C2': 17,
        '#D2': 19,
        '#E2': 21,
        '#F2': 22,
        '#G2': 24,
        '#A2': 26,
        '#B2': 28,
        '!C2': 15,
        '!D2': 17,
        '!E2': 19,
        '!F2': 20,
        '!G2': 22,
        '!A2': 24,
        '!B2': 26,

        'C3': 28,
        'D3': 30,
        'E3': 32,
        'F3': 33,
        'G3': 35,
        'A3': 37,
        'B3': 39,
        '#C3': 29,
        '#D3': 31,
        '#E3': 33,
        '#F3': 34,
        '#G3': 36,
        '#A3': 38,
        '#B3': 40,
        '!C3': 27,
        '!D3': 29,
        '!E3': 31,
        '!F3': 32,
        '!G3': 34,
        '!A3': 36,
        '!B3': 38,

        'C4': 40,
        'D4': 42,
        'E4': 44,
        'F4': 45,
        'G4': 47,
        'A4': 49,
        'B4': 51,
        '#C4': 41,
        '#D4': 43,
        '#E4': 45,
        '#F4': 46,
        '#G4': 48,
        '#A4': 50,
        '#B4': 52,
        '!C4': 39,
        '!D4': 41,
        '!E4': 43,
        '!F4': 44,
        '!G4': 46,
        '!A4': 48,
        '!B4': 50,

        'C5': 52,
        'D5': 54,
        'E5': 56,
        'F5': 57,
        'G5': 59,
        'A5': 61,
        'B5': 63,
        '#C5': 53,
        '#D5': 55,
        '#E5': 57,
        '#F5': 58,
        '#G5': 60,
        '#A5': 62,
        '#B5': 64,
        '!C5': 51,
        '!D5': 53,
        '!E5': 55,
        '!F5': 56,
        '!G5': 58,
        '!A5': 60,
        '!B5': 62,

        'C6': 64,
        'D6': 66,
        'E6': 68,
        'F6': 69,
        'G6': 71,
        'A6': 73,
        'B6': 75,
        '#C6': 65,
        '#D6': 67,
        '#E6': 69,
        '#F6': 70,
        '#G6': 72,
        '#A6': 74,
        '#B6': 76,
        '!C6': 63,
        '!D6': 65,
        '!E6': 67,
        '!F6': 68,
        '!G6': 70,
        '!A6': 72,
        '!B6': 74,

        'C7': 76,
        'D7': 78,
        'E7': 80,
        'F7': 81,
        'G7': 83,
        'A7': 85,
        'B7': 87,
        '#C7': 77,
        '#D7': 79,
        '#E7': 81,
        '#F7': 82,
        '#G7': 84,
        '#A7': 86,
        '#B7': 88,
        '!C7': 75,
        '!D7': 77,
        '!E7': 79,
        '!F7': 80,
        '!G7': 82,
        '!A7': 84,
        '!B7': 86,

        'C8': 88,
        '#B8': 88,
    };

    // 国际标准音
    private static standard: PianoSymbol = 'A4';

    // 国际标准音的映射数值
    private static standardNumValue: number = PianoUtil.mapping[PianoUtil.standard];

    // 国际标准音的频率值
    private static standardFrequency: number = 440;

    // 公比
    private static rate: number = Math.pow(2, 1/12);

    // 音频上下文
    private static ctx: AudioContext | null = null;

    /**
     * 音的记法：
     *      普通音：C1、D4、E2
     *      升音：#C3、#F4
     *      降音：!C3、!F3
     * 
     * 钢琴键盘所有音：
     * 
     *      A0 B0                   #A0                 !B0 !C0
     *      C1 D1 E1 F1 G1 A1 B1    #C1 #D1 #E1 #F1 #G1 #A1 #B1     !C1 !D1 !E1 !F1 !G1 !A1 !B1
     *      C2 D2 E2 F2 G2 A2 B2    #C2 #D2 #E2 #F2 #G2 #A2 #B2     !C2 !D2 !E2 !F2 !G2 !A2 !B2
     *      C3 D3 E3 F3 G3 A3 B3    #C3 #D3 #E3 #F3 #G3 #A3 #B3     !C3 !D3 !E3 !F3 !G3 !A3 !B3
     *      C4 D4 E4 F4 G4 A4 B4    #C4 #D4 #E4 #F4 #G4 #A4 #B4     !C4 !D4 !E4 !F4 !G4 !A4 !B4
     *      C5 D5 E5 F5 G5 A5 B5    #C5 #D5 #E5 #F5 #G5 #A5 #B5     !C5 !D5 !E5 !F5 !G5 !A5 !B5
     *      C6 D6 E6 F6 G6 A6 B6    #C6 #D6 #E6 #F6 #G6 #A6 #B6     !C6 !D6 !E6 !F6 !G6 !A6 !B6
     *      C7 D7 E7 F7 G7 A7 B7    #C7 #D7 #E7 #F7 #G7 #A7 #B7     !C7 !D7 !E7 !F7 !G7 !A7 !B7
     * 
     *      
     *      C8  #B8
     * 
     * 
     * MucicSymbol
     */


    /**
     * 计算指定钢琴琴键的发声频率
     * @param symbol { MusicSymbol } 需要计算频率的琴键的音名（基于科学音调命名法）
     * @returns 计算出来的频率，单位：Hz
     */
    public static frequency(symbol: PianoSymbol): number {
        const dstNumValue = PianoUtil.mapping[symbol];
        const diff = dstNumValue - PianoUtil.standardNumValue;
        const sign = diff<0 ? -1 : 1;
        const f = PianoUtil.standardFrequency * Math.pow(Math.pow(PianoUtil.rate, Math.abs(diff)), sign);
        return f;
    }

    /**
     * 播放一个钢琴上的音
     * @param symbol { PianoSymbol } 需要播放的音的音名
     * @param duration { number } 音持续时长，单位：ms
     */
    public static playSound(symbol: PianoSymbol, duration: number) {
        // 0. 音频上下文
        if (PianoUtil.ctx===null) {
            PianoUtil.ctx = new AudioContext();
        }
        const ctx = PianoUtil.ctx;
        // 1. 振荡器
        const oscillator = ctx.createOscillator();
        // 2. 计算频率
        const frequency = PianoUtil.frequency(symbol);
        // 3. 设置频率
        oscillator.frequency.setValueAtTime(frequency, ctx.currentTime);
        // 4. 设置音色
        oscillator.type = 'square';
        // 5. 增益节点
        const gainNode = ctx.createGain();
        // 6. 设置音量
        gainNode.gain.setValueAtTime(0, ctx.currentTime);
        gainNode.gain.linearRampToValueAtTime(1, ctx.currentTime+0.02);
        // 7. 连接
        oscillator.connect(gainNode); 
        gainNode.connect(ctx.destination); 
        // 8. 播放
        oscillator.start();
        // 9. 一段时间后停止
        setTimeout(() => {
            oscillator.stop();
            oscillator.disconnect();
            gainNode.disconnect();
        }, duration);

    }
}

export {
    PianoUtil,
};